package com.liu.project.kmeans;

import com.liu.project.pojo.CKmeas;
import com.liu.project.pojo.Certificate;
import com.liu.project.service.CertificateService;
import com.liu.project.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;

import java.text.DecimalFormat;
import java.util.*;

/**
 * @program: project
 * @description: 聚类算法
 * @author: 86187
 * @create: 2022-03-17 17:03
 **/
@Configuration
public class kmeans {

    RedisUtil redisUtil;

    CertificateService certificateService;

    public kmeans(RedisUtil redisUtil, CertificateService certificateService) {
        this.certificateService=certificateService;
        this.redisUtil=redisUtil;
    }



    private DecimalFormat df = new DecimalFormat("#####.00000");
    public Kmeans_data data = null;
    // feature,样本名称和索引映射
    private Map<String, Integer> identifier = new HashMap<String, Integer>();

    private Map<Integer, String> iden0 = new HashMap<Integer, String>();
    private ClusterModel model = new ClusterModel();


    /**
     * 数据库到矩阵的映射
     * @return
     * @throws Exception
     */
    public double[][] jdbcToMatrix() {

        List<CKmeas> all = certificateService.getXAndY();

        model.identifier = identifier;
        model.iden0 = iden0;

        int rows = all.size();
        double[][] da = new double[rows][2];

        for (int i=0;i<all.size();i++){
            identifier.put(String.valueOf(all.get(i).getCid()),i);
            iden0.put(i,String.valueOf(all.get(i).getCid()));
               da[i][0]=all.get(i).getSquantizer();//x
               da[i][1]=all.get(i).getPquantizer();//y
        }

        return da;
    }

    /**
     * 清零操作
     * @param matrix
     * @param highDim
     * @param lowDim
     */
    private void setDouble2Zero(double[][] matrix, int highDim, int lowDim) {
        for (int i = 0; i < highDim; i++) {
            for (int j = 0; j < lowDim; j++) {
                matrix[i][j] = 0;
            }
        }
    }

    /**
     * 聚类中心拷贝
     * @param dests
     * @param sources
     * @param highDim
     * @param lowDim
     */
    private void copyCenters(double[][] dests, double[][] sources, int highDim, int lowDim) {
        for (int i = 0; i < highDim; i++) {//行
            for (int j = 0; j < lowDim; j++) {//列
                dests[i][j] = sources[i][j];//将数组sources导入dests
            }
        }
    }

    /**
     * 更新聚类中心
     * @param k
     * @param data
     */
    private void updateCenters(int k, Kmeans_data data) {
        double[][] centers = data.centers;
        setDouble2Zero(centers, k, data.dim);//聚类中心归零
        int[] labels = model.labels;//样本所属类别
        int[] centerCounts = model.centerCounts;//样本个数
        for (int i = 0; i < data.dim; i++) {
            for (int j = 0; j < data.length; j++) {
                centers[labels[j]][i] += data.data[j][i];
            }
        }
        for (int i = 0; i < k; i++) {
            for (int j = 0; j < data.dim; j++) {
                centers[i][j] = centers[i][j] / centerCounts[i];
            }
        }
    }

    /**
     * 计算欧氏距离
     * @param pa
     * @param pb
     * @param dim
     * @return
     */
    public double dist(double[] pa, double[] pb, int dim) {
        double rv = 0;
        for (int i = 0; i < dim; i++) {
            double temp = pa[i] - pb[i];
            temp = temp * temp;
            rv += temp;
        }
        return Math.sqrt(rv);
    }

    /**
     * 样本训练,需要人为设定k值(聚类中心数目)
     * @param k
     * @param //data
     * @return
     * @throws Exception
     */
    public ClusterModel train(int k) {
        double[][] matrix = jdbcToMatrix();
        data = new Kmeans_data(matrix, matrix.length, matrix[0].length);
        return train(k, new Kmeans_param());
    }

    private ClusterModel train(int k, Kmeans_param param) {
        // 首先进行数据归一化处理
        normalize(data);

        double[][] centers = new double[k][data.dim];
        data.centers = centers;
        int[] centerCounts = new int[k];
        model.centerCounts = centerCounts;
        Arrays.fill(centerCounts, 0);//数组归0
        int[] labels = new int[data.length];//聚类类别
        model.labels = labels;
        double[][] oldCenters = new double[k][data.dim];


        int centerIndexes[] = new int[k];
        int countCenter = 0;
        int count = 0;//可聚类中心数
        centerIndexes[0] = 0;
        countCenter++;//聚类中心 最小1
        for (int i = 1; i < data.length; i++) {//统计可聚类中心总数
            for (int j = 0; j < countCenter; j++) {
                if (dist(data.data[i], data.data[centerIndexes[j]], data.dim) > param.min_euclideanDistance) {
                    count++;//如何距离大于最小差值 可聚类中心加1
                }
            }
            if (count == countCenter) {
                centerIndexes[countCenter++] = i;
            }
            count = 0;

            if (countCenter == k) {//循环结束条件
                break;
            }

            if (countCenter < k && i == data.length - 1) {//循环结束条件
                k = countCenter;//计算后的中心数量
                break;
            }
        }

        for (int i = 0; i < k; i++) {//通过索引将中心点坐标存入数组
            int m = centerIndexes[i];
            for (int j = 0; j < data.dim; j++) {
                centers[i][j] = data.data[m][j];
            }
        }


        model.originalCenters = new double[k][data.dim];
        for (int i = 0; i < k; i++) {
            for (int j = 0; j < data.dim; j++) {
                model.originalCenters[i][j] = centers[i][j];//存入原始中心
            }
        }


        for (int i = 0; i < data.length; i++) {
            double minDist = dist(data.data[i], centers[0], data.dim);
            int label = 0;//坐标所属类
            for (int j = 1; j < k; j++) {
                double tempDist = dist(data.data[i], centers[j], data.dim);//计算每个点与每个中心的距离
                if (tempDist < minDist) {
                    minDist = tempDist;
                    label = j;//该坐标归入该类
                }
            }
            labels[i] = label;//将类别对照存储
            centerCounts[label]++;//该样本个数加1
        }

        updateCenters(k, data);
        copyCenters(oldCenters, centers, k, data.dim);

        int maxAttempts = param.attempts > 0 ? param.attempts : Kmeans_param.MAX_ATTEMPTS;
        int attempts = 1;
        double criteria = param.criteria > 0 ? param.criteria : Kmeans_param.MIN_CRITERIA;
        double criteriaBreakCondition = 0;

        boolean[] flags = new boolean[k];

        iterate: while (attempts < maxAttempts) {
            for (int i = 0; i < k; i++) {
                flags[i] = false;
            }
            for (int i = 0; i < data.length; i++) {
                double minDist = dist(data.data[i], centers[0], data.dim);
                int label = 0;
                for (int j = 1; j < k; j++) {
                    double tempDist = dist(data.data[i], centers[j], data.dim);
                    if (tempDist < minDist) {
                        minDist = tempDist;
                        label = j;
                    }
                }
                if (label != labels[i]) {
                    int oldLabel = labels[i];
                    labels[i] = label;
                    centerCounts[oldLabel]--;
                    centerCounts[label]++;
                    flags[oldLabel] = true;
                    flags[label] = true;
                }
            }
            updateCenters(k, data);
            attempts++;

            double maxDist = 0;
            for (int i = 0; i < k; i++) {
                if (flags[i]) {
                    double tempDist = dist(centers[i], oldCenters[i], data.dim);
                    if (maxDist < tempDist) {
                        maxDist = tempDist;
                    }
                    for (int j = 0; j < data.dim; j++) { // 锟斤拷锟斤拷oldCenter
                        oldCenters[i][j] = centers[i][j];
                        oldCenters[i][j] = Double.valueOf(df.format(oldCenters[i][j]));
                    }
                }
            }
            if (maxDist < criteria) {
                criteriaBreakCondition = maxDist;
                break iterate;
            }
        }

        ClusterModel rvInfo = outputClusterInfo(criteriaBreakCondition, k, attempts, param, centerCounts);
        redis(rvInfo);
        return rvInfo;
    }

    /**
     * 把聚类结果存储到Model中
     * @param criteriaBreakCondition
     * @param k
     * @param attempts
     * @param param
     * @param centerCounts
     * @return
     */
    private ClusterModel outputClusterInfo(double criteriaBreakCondition, int k, int attempts, Kmeans_param param,
                                           int[] centerCounts) {
        model.data = data;
        model.k = k;
        int perm[] = new int[data.length];
        model.perm = perm;
        int start[] = new int[k];
        model.start = start;
        group_class(perm, start, k, data);
        return model;
    }

    /**
     * 把聚类样本按所属类别连续存储
     * @param perm
     * @param start
     * @param k
     * @param data
     */
    private void group_class(int perm[], int start[], int k, Kmeans_data data) {

        start[0] = 0;
        for (int i = 1; i < k; i++) {
            start[i] = start[i - 1] + model.centerCounts[i - 1];
        }

        for (int i = 0; i < data.length; i++) {
            perm[start[model.labels[i]]++] = i;
        }

        start[0] = 0;
        for (int i = 1; i < k; i++) {
            start[i] = start[i - 1] + model.centerCounts[i - 1];
        }
    }

    /**
     * 数据归一化处理
     */
   static  Map<Integer, Double[]> minAndMax = new HashMap<Integer, Double[]>();

    private void normalize(Kmeans_data data) {
        for (int i = 0; i < data.dim; i++) {
            Double[] nums = new Double[2];
            double max = data.data[0][i];
            double min = data.data[data.length - 1][i];
            for (int j = 0; j < data.length; j++) {
                if (data.data[j][i] > max) {
                    max = data.data[j][i];
                }
                if (data.data[j][i] < min) {
                    min = data.data[j][i];
                }
            }
            nums[0] = min;
            nums[1] = max;
            minAndMax.put(i, nums);
        }
        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data.dim; j++) {
                double minValue = minAndMax.get(j)[0];
                double maxValue = minAndMax.get(j)[1];
                data.data[i][j] = (data.data[i][j] - minValue) / (maxValue - minValue);
                data.data[i][j] = Double.valueOf(df.format(data.data[i][j]));
            }
        }
    }

    //坐标归一化,用于做查询redis缓存的key
    public double normalize_x(double x){
                double minValue_x = minAndMax.get(0)[0];
                double maxValue_x = minAndMax.get(0)[1];
                x = (x- minValue_x) / (maxValue_x - minValue_x);
               x = Double.valueOf(df.format(x));
                 return x;
    }
    //坐标归一化,用于做查询redis缓存的key
    public double normalize_y(double y){
        double minValue_y = minAndMax.get(1)[0];
        double maxValue_y = minAndMax.get(1)[1];
        y = (y- minValue_y) / (maxValue_y - minValue_y);
        y = Double.valueOf(df.format(y));
        return y;
    }
    /*
    * 将model数据缓存到redis
    * key 为  x,y  聚类中心坐标
    * value 为cid
    * */

    public void redis(ClusterModel model){

//        int originalCount = 0;
//        for (int i = 0; i < model.originalCenters.length; i++) {//每个类
//            double a = model.originalCenters[i][0];//x
//            double b = model.originalCenters[i][1];//y
//            int index = model.labels[model.perm[model.start[i]]];
//            int counts = model.centerCounts[index];
//            originalCount += counts;
//            List<Certificate> objects1=new ArrayList<>();
//            for (int y = model.start[i]; y < originalCount; y++) {
//                Certificate certificateById = certificateService.findCertificateById(Integer.parseInt(iden0.get(model.perm[y])));
//                objects1.add(certificateById);
//            }
//            redisUtil.set(a+","+b,objects1);
//        }
        redisUtil.set("kmeans",model);
    }




}
